#include <cnet.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "queue.c"
#include "nl_table.c"
#include "dll_basic.c"
#include "nl_packet.h"

#define MAXHOPS         4

/*  This file implements a much better flooding algorithm than those in
 both flooding1.c and flooding2.c. As Network Layer packets are processed,
 the information in their headers is used to update the NL table.

 The minimum observed hopcount to each (potential) remote destination
 is remembered by the NL table, as is the link on which these packets
 arrive. This link is later used to route packets leaving for that node.

 The routine NL_savehopcount() is called for both NL_DATA and NL_ACK
 packets, and we even "steal" information from Network Layer packets
 that don't belong to us!

 I don't think it's a flooding algorithm any more Toto.

 This flooding algorithm exhibits an efficiency which improves over time
 (as the NL table "learns" more).  Over the 8 nodes in the AUSTRALIA.MAP
 file, the initial efficiency is the same as that of flooding1.c (about
 2%) but as the NL table improves, the efficiency rises to over 64%.
 */


#define PACKET_HEADER_SIZE  (sizeof(NL_PACKET) - MAX_MESSAGE_SIZE)
#define PACKET_SIZE(p)      (PACKET_HEADER_SIZE + p.length)

// static char receiveBuffer[255][255][MAX_MESSAGE_SIZE];
static char *rb[255][255];
NL_PACKET * lastPacket;

void printmsg(char * msg, size_t length) {
        size_t i;
        //("msg :");
        for (i = 0; i < length; i++) {
                //("%d", (unsigned int) msg[i]);
        }
        //("\n");
}

void update_last_packet(NL_PACKET *last) {
        int index = find_address(last->dest);
        //NL_PACKET * lastsend = &(NL_table[index].lastpacket);

        memcpy(&(NL_table[index].lastpacket), last, PACKET_SIZE((*last)));
        //free(temp);
}

NL_PACKET * get_last_packet(CnetAddr address) {
        int index = find_address(address);
        return (NL_PACKET *) &(NL_table[index].lastpacket);
}

// here the second parameter is the length of msg of packet!
void sendPacketPiecesToDatalink(char *packet, size_t length, int choose_link) {
        //("sendPacketPiecesToDatalink\n");

        //("linkinfo[choose_link] %d\n", linkinfo[choose_link].mtu);
        size_t maxPacketLength = linkinfo[choose_link].mtu - PACKET_HEADER_SIZE;

        NL_PACKET *tempPacket = (NL_PACKET *) packet;

        size_t tempLength = length;
        char *str = tempPacket->msg;

        NL_PACKET piecePacket;

        piecePacket.src = tempPacket->src;
        piecePacket.dest = tempPacket->dest;
        piecePacket.kind = tempPacket->kind;
        piecePacket.seqno = tempPacket->seqno;
        piecePacket.hopcount = tempPacket->hopcount;
        piecePacket.pieceEnd = tempPacket->pieceEnd;
        piecePacket.pieceNumber = tempPacket->pieceNumber;
        piecePacket.checksum = tempPacket->checksum;

        while (tempLength > maxPacketLength) {

                piecePacket.length = maxPacketLength;
                memcpy(piecePacket.msg, str, maxPacketLength);

                CHECK(down_to_datalink(choose_link, (char *) &piecePacket,
                                PACKET_SIZE(piecePacket)));
                //("piece %d down_to_datalink\n", piecePacket.pieceNumber);

                str = str + maxPacketLength;
                piecePacket.pieceNumber = piecePacket.pieceNumber + 1;

                tempLength = tempLength - maxPacketLength;
        }

        piecePacket.pieceEnd = 1;
        piecePacket.length = tempLength;

        memcpy(piecePacket.msg, str, tempLength);
        ////("Required link is provided link = %d\n", choose_link);
        CHECK(down_to_datalink(choose_link, (char *) &piecePacket,
                        PACKET_SIZE(piecePacket)));
        //("last piece %d down_to_datalink\n", piecePacket.pieceNumber);
        ////("Provided link = %d sent! \n", choose_link);
}

/* ----------------------------------------------------------------------- */

/*  flood3() IS A BASIC ROUTING STRATEGY WHICH TRANSMITS THE OUTGOING PACKET
 ON EITHER THE SPECIFIED LINK, OR ALL BEST-KNOWN LINKS WHILE AVOIDING
 ANY OTHER SPECIFIED LINK.
 */
static void flood3(char *packet, size_t length, int choose_link, int avoid_link) {

        //("flood3\n");
        NL_PACKET *p = (NL_PACKET *) packet;

        /*  REQUIRED LINK IS PROVIDED - USE IT */
        if (choose_link != 0) {
                sendPacketPiecesToDatalink(packet, p->length, choose_link);
        }

        /*  OTHERWISE, CHOOSE THE BEST KNOWN LINKS, AVOIDING ANY SPECIFIED ONE */
        else {
                int links_wanted = NL_linksofminhops(p->dest);
                int link;

                for (link = 1; link <= nodeinfo.nlinks; ++link) {

                        if (link == avoid_link) /* possibly avoid this one */{
                                continue;
                        }
                        if (links_wanted & (1 << link)) /* use this link if wanted */
                        {


                                sendPacketPiecesToDatalink(packet, p->length, link);

                        }
                }
        }
}

/*  down_to_network() RECEIVES NEW MESSAGES FROM THE APPLICATION LAYER AND
 PREPARES THEM FOR TRANSMISSION TO OTHER NODES.
 */
static EVENT_HANDLER( down_to_network) {
        NL_PACKET p;

        p.length = sizeof(p.msg);
        CHECK(CNET_read_application(&p.dest, p.msg, &p.length));
        CNET_disable_application(p.dest);

        p.src = nodeinfo.address;
        p.kind = NL_DATA;
        p.hopcount = 0;
        p.seqno = NL_nextpackettosend(p.dest);
        p.pieceNumber = 0;
        p.pieceEnd = 0;

        p.checksum = CNET_ccitt((unsigned char *) (p.msg), p.length);
        lastPacket = &p;

        flood3((char *) &p, PACKET_SIZE(p), 0, 0);
        update_last_packet(&p);

}

//static int err_count = 0;

/*  up_to_network() IS CALLED FROM THE DATA LINK LAYER (BELOW) TO ACCEPT
 A PACKET FOR THIS NODE, OR TO RE-ROUTE IT TO THE INTENDED DESTINATION.
 */
int up_to_network(char *packet, size_t length, int arrived_on_link) {
        NL_PACKET *p = (NL_PACKET *) packet;

        //("up to network\n");
        ++p->hopcount; /* took 1 hop to get here */
        ////("me = %d, dest = %d =======\n", nodeinfo.address, p->dest);
        /*  IS THIS PACKET IS FOR ME? */
        if (p->dest == nodeinfo.address) {
                switch (p->kind) {
                case NL_DATA:
                        if (p->seqno == NL_packetexpected(p->src)) {
                            
                                if(p->pieceNumber == 0){
                                    rb[p->src][p->dest] = malloc(MAX_MESSAGE_SIZE);
                                }
                                length = p->length;
                                memcpy(rb[p->src][p->dest], (char *) p->msg, length);
                                rb[p->src][p->dest] = rb[p->src][p->dest] + length;

                                if (p->pieceEnd) {
                                        CnetAddr tmpaddr;

                                        length = p->pieceNumber * (linkinfo[arrived_on_link].mtu
                                                        - PACKET_HEADER_SIZE) + p->length;
                                        int p_checksum = p->checksum;
                                        int checksum = CNET_ccitt(
//                                                         (unsigned char *) (receiveBuffer[p->src][p->dest]),
//                                                         (int) length);
                                                        (unsigned char *) (rb[p->src][p->dest]),
                                                        (int) length);
                                        if (p_checksum != checksum) {
                                                /***************************send back error ack**************/
                                                NL_savehopcount(p->src, p->hopcount, arrived_on_link);

                                                tmpaddr = p->src; /* swap src and dest addresses */
                                                p->src = p->dest;
                                                p->dest = tmpaddr;

                                                p->kind = NL_ERR_ACK;
                                                p->hopcount = 0;
                                                p->length = 0;
                                                flood3(packet, PACKET_HEADER_SIZE, arrived_on_link, 0);
                                                
                                                free(rb[p->dest][p->src]);
//                                                 rb[p->dest][p->src] = &receiveBuffer[p->src][p->dest][0];
                                                return 0;
                                                /***************************end******************************/
                                        }
//                                         if (CNET_write_application(receiveBuffer[p->src][p->dest], &length)
//                                                         == -1) 
                                            
                                        if (CNET_write_application(rb[p->src][p->dest], &length)
                                                    == -1)
                                        
                                        {
                                                //source node should send this msg again
                                                //printf("===\ncnet_errno = %d\n===\n", cnet_errno);
                                                //printf("error count: %d\n", ++ err_count);
                                                if (cnet_errno == ER_CORRUPTFRAME) {
                                                        //printf("\nWarning: frame corrupted\n==\n");
                                                } else if (cnet_errno == ER_MISSINGMSG) {
                                                        //printf("\nWarning: frame missed\n\n");
                                                }
                                        }

//                                         rb[p->src][p->dest] = &receiveBuffer[p->src][p->dest][0];
                                        free(rb[p->src][p->dest]);

                                        inc_NL_packetexpected(p->src);

                                        NL_savehopcount(p->src, p->hopcount, arrived_on_link);

                                        tmpaddr = p->src; /* swap src and dest addresses */
                                        p->src = p->dest;
                                        p->dest = tmpaddr;

                                        p->kind = NL_ACK;
                                        p->hopcount = 0;
                                        p->length = 0;
                                        flood3(packet, PACKET_HEADER_SIZE, arrived_on_link, 0);

                                }
                        }
                        break;

                case NL_ACK:
                        if (p->seqno == NL_ackexpected(p->src)) {
                                ////("ACK come!\n");
                                inc_NL_ackexpected(p->src);
                                NL_savehopcount(p->src, p->hopcount, arrived_on_link);
                                CHECK(CNET_enable_application(p->src));
                        }
                        break;
                case NL_ERR_ACK:
                        printf("ERROR ACK!\n");
                        if (p->seqno == NL_ackexpected(p->src)) {
                                NL_savehopcount(p->src, p->hopcount, arrived_on_link);
                                printf("packet %d (to %d) error!\n", p->seqno, p->src);
                                /*
                                NL_PACKET * packettoresend = get_last_packet(p->src);
                                printf("length = %d seq = %d\n", packettoresend->length,
                                                packettoresend->seqno);

                                int a = packettoresend->checksum;
                                int b = CNET_ccitt((unsigned char *) (packettoresend->msg),
                                                (int) (packettoresend->length));
                                if (a == b) {
                                        printf("ok!\n");
                                } else
                                        printf("wrong!\n");
                                int len = PACKET_HEADER_SIZE + packettoresend->length;
                                flood3((char *) packettoresend, len, 0, 0);
                                */

                        }
                        break;
                default:
                        //("it's nothing!====================\n");
                        break;
                }
        }
        /* THIS PACKET IS FOR SOMEONE ELSE */
        else {
                //              //("hopcount = %d\n", p->hopcount);
                //              //("MAXHOPS = %d\n", MAXHOPS);
                if (p->hopcount < MAXHOPS) { /* if not too many hops... */
                        ////("other's frame!\n");

                        //("piece for another node arrives\n");
                        if(p->pieceNumber == 0){
                            rb[p->src][p->dest] = malloc(MAX_MESSAGE_SIZE);
                        }
                        length = p->length;
                        memcpy(rb[p->src][p->dest], (char *) p->msg, length);
                        rb[p->src][p->dest] = rb[p->src][p->dest] + length;

                        if (p->pieceEnd) {
                                //("last piece for another node arrives\n");
                                length = p->pieceNumber * (linkinfo[arrived_on_link].mtu
                                                - PACKET_HEADER_SIZE) + p->length;

                                NL_savehopcount(p->src, p->hopcount, arrived_on_link);

                                NL_PACKET wholePacket;

                                wholePacket.src = p->src;
                                wholePacket.dest = p->dest;
                                wholePacket.kind = p->kind;
                                wholePacket.seqno = p->seqno;
                                wholePacket.hopcount = p->hopcount;
                                wholePacket.pieceEnd = 0;
                                wholePacket.pieceNumber = 0;
                                wholePacket.length = length;

//                                 memcpy(wholePacket.msg, receiveBuffer[p->src][p->dest], length);
                                memcpy(wholePacket.msg, rb[p->src][p->dest], length);

                                //                             //("contents of msg forwarding is \n %s\n", receiveBuffer[p->src]);
                                flood3((char *) &wholePacket, PACKET_SIZE(wholePacket), 0,
                                                arrived_on_link);
//                                 rb[p->src][p->dest] = &receiveBuffer[p->src][p->dest][0];
                                free(rb[p->src][p->dest]);

                        }

                }

                        //("drop\n");
                        /* silently drop */;
        }
        return (0);
}

/* ----------------------------------------------------------------------- */

EVENT_HANDLER( reboot_node) {
        if (nodeinfo.nlinks > 32) {
                //              f//(stderr, "flood3 flooding will not work here\n");
                exit(1);
        }

//         for (int i = 0; i <= 255; i++) {
//             for (int j = 0; j <= 255; j++){
//                 rb[i][j] = receiveBuffer[i][j];
//             }
//         }
        

        reboot_DLL();
        reboot_NL_table();

        CHECK(CNET_set_handler(EV_APPLICATIONREADY, down_to_network, 0));
        CHECK(CNET_enable_application(ALLNODES));
}
